﻿using nhCore.Modbus;
using nhCore.Server;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace nhCore
{
    /// <summary>
    /// 不同接口都在这处理连接、断开、放回、发送
    /// </summary>
    public class NhServer : IIOListener, ISvrListener
    {
        #region "属性"    

        private IDB Database { get; set; }
        private DicEnvStat DicEnvStat { set; get; }
        public bool IgnoreFailedSyncMode { get; set; }

        /// <summary>
        /// IO模式，com|tcpServer|usb
        /// </summary>
        public List<IO> Ios { get; set; }

        /// <summary>
        /// 服务是否在运行
        /// </summary>
        private bool IsRuning { get; set; }

        /// <summary>
        /// 连接集合
        /// </summary>
        private List<Conn> Conns { get; set; } = new List<Conn>();

        /// <summary>
        /// 命令解析辅助对象集。要素字典，设备地址为键
        /// </summary>
        public Dictionary<byte, CmdAnalysisHelp> CmdAnalysisHelps { get; set; } = new Dictionary<byte, CmdAnalysisHelp>();

        /// <summary>
        /// 服务监听器，当前对象转换为ISvrListener类型的对象的属性，可以直接通过该属性来调用接口方法
        /// </summary>
        private ISvrListener SvrListener { get; set; }

        private IMessage MsgListener { get; set; }

        private IAnalysisToDisplay AnalysisToDisplay { get; set; }
        #endregion

        #region "实现IIOListener接口"
        /// <summary>
        /// 异步模式下,设备有数据回来
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="client"></param>
        /// <param name="byt"></param>
        void IIOListener.Received(IO sender, object client, byte[] byt)
        {

        }
        void IIOListener.DisConnected(IO sender, object client)
        {
            Conn conn;
            lock (Conns)
            {
                conn = Conns.Find(x => x.Client == client);
            }
            if (conn == null)
            {
                return;
            }
            conn.IsRunning = false; //关task
            lock (Conns)
            {
                Conns.Remove(conn);
            }

            if (client is System.IO.Ports.SerialPort sp)//客户端是串口则已关闭
            {
                string str = $"{sp.PortName}接口已关闭。";
                lock (Conns)
                {
                    if (Conns.Find(x => x.Client is System.IO.Ports.SerialPort) == null)
                    {
                        str += "所有COM接口均已关闭。";
                    }
                }
                MsgListener.Message(str);
            }
            else if (client is System.Net.Sockets.TcpClient tc)//客户端是串口则已关闭
            {
                string str = $"客户端[{conn.ClientAddress}]已离线。";//显示IP、设备地址
                MsgListener.Message(str);
            }
            else if (sender is YunLinkIO)
            {
                MsgListener.Message($"云链接[{conn.ClientAddress}]已离线。");
            }
        }

        /// <summary>
        /// 有客户端连接上来，每一个连接对应一个Conn对象，每个对象有自己的发送接受线程
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="client"></param>
        void IIOListener.Connected(IO sender, object client)
        {
            List<Cmd> ls = new List<Cmd>();
            lock (CmdAnalysisHelps)
            {
                foreach (CmdAnalysisHelp r in CmdAnalysisHelps.Values) //遍历辅助类
                {
                    foreach (Modbus.CmdFrame sc in r.GetSentCmds())
                    {
                        if (sc.Automatic)//自动发送命令加到临时命令列表
                        {
                            Cmd cmd;
                            if (sender is YunLinkIO)
                            {
                                if (sc is RealTimeCmd)
                                {
                                    cmd = new Cmd(new YunRtCmd(r.Id, r.Address));
                                }
                                else if (sc is HistoryCmd)
                                {
                                    cmd = new Cmd(new YunHistoryCmd(r.Id, sc.Address));
                                }
                                else
                                {
                                    break;
                                }
                                cmd.SentCmd.Analyzer.Decoders = sc.Analyzer.Decoders;
                            }
                            else
                            {
                                cmd = new Cmd(sc); //命令帮助初始话时DelayTime被赋值
                            }
                            ls.Add(cmd);
                        }
                    }
                }
            }
            Conn conn = new Conn(sender, client, AnalysisResult);
            conn.SetListener(SvrListener, MsgListener);
            lock (Conns)
            {
                Conns.Add(conn);
            }
            conn.Cmds = ls;

            string str;

            if (client is System.IO.Ports.SerialPort)
            {
                str = $"{conn.ClientAddress}接口已打开。";
            }
            else if (client is System.Net.Sockets.TcpClient)
            {
                str = $"客户端[{conn.ClientAddress}]已连接。";
            }
            else if (client is YunLinkIO)
            {
                str = $"云链接[{conn.ClientAddress}]已连接。";
            }
            else
            {
                str = "未知接口连接。";
            }

            MsgListener.Message(str);
        }

        void IIOListener.Error(string text)
        {
            MsgListener.Error(text);
        }
        #endregion

        #region 服务相关
        /// <summary>
        /// 开启服务
        /// </summary>
        /// <returns>是否正常开启</returns>
        public bool Start()
        {
            if (IsRuning)
            {
                Debug.WriteLine("开启服务时发现服务已经在运行..."); //输出调试信息
                return false;
            }

            if (Ios.Count == 0)
            {
                Debug.WriteLine("未开启IO口，请进入设置窗口设置。"); //输出调试信息
                return false;
            }

            string ioName = "";
            foreach (var io in Ios)
            {
                if (io.Open())//根据IO口类型打开对应的端口
                {
                    ioName += ioName == "" ? io.Name : $",{io.Name}";
                }
            }

            Debug.WriteLine($"{ioName}服务开启...");
            return true;
        }

        /// <summary>
        /// 停止服务，关闭端口
        /// </summary>
        public void Stop()
        {
            IsRuning = false;
            CmdAnalysisHelps.Clear();
            if (Ios == null) return;
            foreach (var io in Ios)
            {
                io.Close();
            }
        }

        /// <summary>
        /// 设置IO监听器
        /// </summary>
        /// <param name="io"></param>
        public void SetIO(List<IO> ios)
        {
            Ios = ios;
            foreach (IO io in Ios)
            {
                io.SetIOListener(this);
            }
        }

        /// <summary>
        /// 设置服务监听
        /// </summary>
        /// <param name="Listener"></param>
        public void SetListener(IMessage Listener)
        {
            SvrListener = this;
            MsgListener = (IMessage)Listener;
            AnalysisToDisplay = (IAnalysisToDisplay)Listener;
        }
        #endregion

        /// <summary>
        /// 发送命令，手动，找到在线客户端的自动发送命令，送入Cmds
        /// </summary>
        /// <param name="client">目标客户端，为null时向所有客户端发送</param>
        /// <param name="sentCmd">发送命令</param>
        /// <returns>是否找到对应客户端，命令进入命令列表</returns>
        public bool SendCmd(Modbus.CmdFrame sentCmd, object client = null)
        {
            bool result = false;
            lock (Conns)
            {
                if (client == null)//未指定客户端，向所有设备发送
                {   //配置命令
                    result = true;
                    foreach (Conn c in Conns)//遍历在线客户端列表,对应的命令帮助
                    {
                        Cmd cmd = new Cmd(sentCmd)
                        {
                            //TimeOut = cmdHelper.TimeOut,//对新实例设置
                        };
                        c.Cmds.Add(cmd);//加入命令列表
                    }
                }
                else //向指定客户端发送
                {
                    foreach (Conn c in Conns)
                    {
                        if (c == client)
                        {
                            result = true;
                            Cmd cmd = new Cmd(sentCmd);
                            c.Cmds.Add(cmd);//加入命令列表
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 返回帧解析后调用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="client"></param>
        /// <param name="values"></param>
        /// <param name="sent"></param>
        public void AnalysisResult(IO sender, object values, Cmd cmd)
        {
            nhCore.Modbus.CmdFrame sent = cmd.SentCmd;
            if ((bool)(DicEnvStat?.EnvStats.ContainsKey(sent.Address)))
            {//广播地址回出异常
                DicEnvStat.EnvStats[sent.Address]?.RecordingCommTime();
            }

            if (sent is nhCore.Modbus.RealTimeCmd || sent is YunRtCmd)
            {
                AnalysisToDisplay.RealDataDisplay(values as Weathers);
            }
            else if (sent is nhCore.Modbus.HistoryCmd)
            {
                SaveHistoryData((Weathers)values, sent);
            }
            else if (sent is YunHistoryCmd)
            {
                if ((values as List<Weathers>).Count == 0)
                {   //为空时，建立一个时间为0的时间要素，同步停止提供
                    Weathers ws = new Weathers()
                    {
                        Address = sent.Address,
                    };
                    ws.Values.Add(-1, new Weather()
                    {
                        Value = 0
                    });
                    SaveHistoryData(ws, sent);
                }
                else
                {
                    foreach (Weathers ws in values as List<Weathers>)
                    {
                        SaveHistoryData(ws, sent);
                    }
                }
            }
            else if (sent is nhCore.Modbus.CalibrationTimeCmd)
            {
                if ((bool)values)
                {
                    sent.SleepState = true; //时间校准收到回复后进入休眠
                    MsgListener.Message($"{cmd.SentCmd.Address}号设备校时成功。");
                }
                else
                {
                    MsgListener.Error($"{cmd.SentCmd.Address}号设备校时失败。");
                }
            }
            else if (sent is nhCore.Modbus.ConfigCmd)
            {
                AnalysisToDisplay.AddEnv((Dictionary<byte, List<DbConfig>>)values, cmd);
            }
        }

        /// <summary>
        /// 保存历史数据，即同步数据
        /// </summary>
        /// <param name="weathers"></param>
        /// <param name="sent"></param>
        private void SaveHistoryData(Weathers weathers, nhCore.Modbus.CmdFrame sent)
        {
            SyncState ss = DicEnvStat.EnvStats[weathers.Address].SyncState;

            Weather weaTime = weathers.Values[-1];
            //Debug.Write("\tHistory：" + new DateTime((long)weaTime.Value) );

            if (weaTime.Value == 0)
            {
                //读到解析不了的时间，所有同步停止
                ss.SetSyncFailedCountsMax();
                IgnoreFailedSyncMode = false;
                AnalysisToDisplay.IgnoreFailedSyncModeClear();
            }
            else
            {
                Task.Factory.StartNew(() =>
                {
                    try
                    {
                        if (Database.SaveHistory(weathers))
                        {
                            //历史数据保存成功
                            ss.SyncFailedCounts = 0;
                            ss.SyncCounts += 1;
                        }
                        else //历史数据保存失败
                        {
                            ss.SyncFailedCounts += 1;
                        }
                    }
                    catch (Exception)
                    {
                        ss.SyncFailedCounts += 1;
                        //出异常数据库连接会出问题吗?
                        //IDB odb = db;
                        //var ndb = new Access(AccessPath, AccessPWD);
                        //ndb.Open();
                        //db = ndb;
                        //odb?.Close();
                    }
                });
            }

            if (ss.SyncFaileState() && !(bool)IgnoreFailedSyncMode)
            {
                if (ss.SyncCounts > 0)
                {
                    MsgListener.Message($"{weathers.Address}号设备同步完成，共同步{ss.SyncCounts}条新数据");
                }
                sent.ResetCmd();
                sent.SleepState = true;
                ss.SyncCounts = 0;
            }
        }

        #region 初始化服务
        public void Init(IDB database, DicEnvStat dicEnvStat)
        {
            Database = database;
            DicEnvStat = dicEnvStat;
        }
        /// <summary>
        /// 读出数据库setting表，通讯类型、通讯参数设置服务IO
        /// </summary>
        /// <param name="timeOffset">小时偏移</param>
        /// <returns>通讯类型</returns>
        public string SoftwareSet(out int timeOffset)
        {
            Dictionary<string, string> setting = Database.GetSetting(); //读出设置数据
            timeOffset = setting.ContainsKey("timeoffset") ? int.Parse(setting["timeoffset"]) : 0;
            List<IO> ios = new List<IO>();
            IO io;
            string ioName = "";
            foreach (string mode in setting["mode"].Split(','))
            {
                string modeName = "";
                switch (mode)
                {
                    case "com":
                        io = new SPIO(setting["com"].ToUpper(), Convert.ToInt32(setting["baudrate"]), Convert.ToInt32(setting["stopbits"]));
                        ios.Add(io);
                        modeName = "串口";
                        break;
                    case "srv":
                        io = new TcpServerIO(int.Parse(setting["srvport"]));
                        ios.Add(io);
                        modeName = "TCP服务器";
                        break;
                    case "client":
                        io = new TcpClientIO(setting["address"], Convert.ToInt32(setting["port"]));
                        ios.Add(io);
                        modeName = "云直连";
                        break;
                    case "yunlink":
                        io = new YunLinkIO(setting["sn"], setting["pwd"]);
                        ios.Add(io);
                        modeName = "云平台";
                        break;
                }
                ioName += ioName == "" ? modeName : $"+{modeName}";
            }

            SetIO(ios);
            return $"{ioName}通讯";
        }
        /// <summary>
        /// 从数据库中读出站点数据并设置站点相关控件
        /// </summary>
        public void ReadEnvStat(DicEnvStat dicEnvStat)
        {
            dicEnvStat = DicEnvStat.Singleton;
            dicEnvStat.Init(Database);
            CoreGlobal.RealData = RealData.Singleton(Database, dicEnvStat);
            CoreGlobal.RealData.SaveInterval = (int)(nhCore.SettingsExt.Default.传感器保存间隔 * 60);

            foreach (DbSensor dbSensor in dicEnvStat.EnvStats.Values)
            {
                UInt16 maxReg = (ushort)dbSensor.Configs.Max(c => c.Index);

                DbConfig dbConfig = dbSensor.Configs.Where(c => c.Index == maxReg).First() as DbConfig;
                if (dbConfig.DataType == typeof(Int32) || dbConfig.DataType == typeof(float) || dbConfig.DataType == typeof(byte))//byte代替反向浮点
                {
                    maxReg++;//最后一个寄存器为4字节，则要加1
                }

                ushort minReg = byte.TryParse(dbSensor.Type, out byte code) ?
                    (ushort)dbSensor.Configs.Min(c => c.Index) : (ushort)0;//产生 minReg code 2个参数
                CmdAnalysisHelp cah = new CmdAnalysisHelp(dbSensor.Address, maxReg, minReg, code, dbSensor.Id)
                {
                    Name = dbSensor.Name
                }; //命令解析辅助类

                CoreGlobal.计算要素.Clear();
                foreach (DbConfig c in dbSensor.Configs)
                {
                    nhCore.Modbus.WeatherDecoder decoder;
                    if ((!dbSensor.IsCgq) && (c.Index < 3 || c.Index > 100))
                    {
                        CoreGlobal.计算要素.Add((ushort)c.Index, null);
                        decoder = new nhCore.Modbus.NullDecoder((ushort)c.Index);
                    }
                    else
                    {
                        if (c.DataType == typeof(Int32))
                        {
                            decoder = new nhCore.Modbus.Int32Decoder();
                        }
                        else if (c.DataType == typeof(float))
                        {
                            decoder = new nhCore.Modbus.FloatDecoder();
                        }
                        else if (c.DataType == typeof(byte))//byte 代替反向浮点
                        {
                            decoder = new nhCore.Modbus.FloatReverseDecoder();
                        }
                        else
                        {
                            decoder = new nhCore.Modbus.WeatherDecoder();
                        }

                        decoder.ReturnDataIndex = (UInt16)(c.Index - minReg);
                    }
                    decoder.Config = c;
                    cah.Decoders.Add(decoder); //加气象解码器到列表
                }

                if (dbSensor.IsCgq)
                {
                    cah.Decoders.Add(new nhCore.Modbus.CgqTimeDecoder());
                }
                else
                {
                    cah.Decoders.Add(new nhCore.Modbus.TimeDecoder()); //最后加时间解码器到列表，后面通过列表最后项访问
                }

                CmdAnalysisHelps.Add(cah.Address, cah);
            }
        }
        #endregion

        #region 单例模式
        private static readonly NhServer _nhServer = new NhServer();

        //显式的静态构造函数用来告诉C#编译器在其内容实例化之前不要标记其类型
        static NhServer() { }
        //声明一个私有的构造方法，让外部无法调用这个类的构造方法
        private NhServer()
        {
        }

        /// <summary>
        /// 返回对象单例
        /// </summary>
        public static NhServer Singleton
        {
            get { return _nhServer; }
        }
        #endregion
    }
}
